/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <jni.h>
#include <time.h>
#include <android/log.h>
#include <android/bitmap.h>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#include "mosaic.h"

#define  LOG_TAG    "mosaic"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

/* Set to 1 to enable debug log traces. */
#define DEBUG 0

int bufferWidth = 0;
int bufferHeight = 0;

/* Return current time in milliseconds */
static double now_ms(void)
{
	struct timeval tv;
	gettimeofday(&tv, NULL);
	return tv.tv_sec*1000. + tv.tv_usec/1000.;
}
JNIEXPORT void JNICALL Java_com_android_filter_jni_EffectJNI_nativeFillColor(JNIEnv * env, jobject  obj, int num_geometric,
		jintArray indices, jintArray geometric_colors, jintArray vertices, jobject output_bitmap/*,  jobject output_bitmap*/)
{
	AndroidBitmapInfo  /*input_info,*/ output_info;
	void              /* *input_pixels,*/ *output_pixels;
	int                ret;
	jint *indices_array;
	jint *geometric_colors_array;
	jint *vertices_array;

	     // get a pointer to the array
	indices_array = (*env)->GetIntArrayElements(env, indices, NULL);
	geometric_colors_array = (*env)->GetIntArrayElements(env, geometric_colors, NULL);
	vertices_array = (*env)->GetIntArrayElements(env, vertices, NULL);
	/*if ((ret = AndroidBitmap_getInfo(env, input_bitmap, &input_info)) < 0) {
		LOGE("input_bitmap AndroidBitmap_getInfo() failed ! error=%d", ret);
		return;
	}

	if (input_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
		LOGE("Bitmap format is not RGB_888 !");
		return;
	}

	if ((ret = AndroidBitmap_lockPixels(env, input_bitmap, &input_pixels)) < 0) {
		LOGE("input_pixels AndroidBitmap_lockPixels() failed ! error=%d", ret);
	}*/

	if ((ret = AndroidBitmap_getInfo(env, output_bitmap, &output_info)) < 0) {
		LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
		return;
	}

	if (output_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
		LOGE("Bitmap format is not RGB_888 !");
		return;
	}

	if ((ret = AndroidBitmap_lockPixels(env, output_bitmap, &output_pixels)) < 0) {
		LOGE("output_pixels AndroidBitmap_lockPixels() failed ! error=%d", ret);
	}
//	GeometricList* list = getGeometricList();
//	if(list != 0)
//	{
		fill_color(num_geometric, indices_array, geometric_colors_array, vertices_array, output_pixels, output_info.width, output_info.height, 0 );
		freeVertexList();
//	}
	(*env)->ReleaseIntArrayElements(env, indices, indices_array, 0);
	(*env)->ReleaseIntArrayElements(env, geometric_colors, geometric_colors_array, 0);
	(*env)->ReleaseIntArrayElements(env, vertices, vertices_array, 0);
	//AndroidBitmap_unlockPixels(env, input_bitmap);
	AndroidBitmap_unlockPixels(env, output_bitmap);
}
JNIEXPORT void JNICALL Java_com_android_filter_jni_EffectJNI_nativeMosaic(JNIEnv * env, jobject  obj, jobject input_bitmap/*,  jobject output_bitmap*/)
{
	AndroidBitmapInfo  input_info/*, output_info*/;
	void              *input_pixels/*, *output_pixels*/;
	int                ret;

	if ((ret = AndroidBitmap_getInfo(env, input_bitmap, &input_info)) < 0) {
		LOGE("input_bitmap AndroidBitmap_getInfo() failed ! error=%d", ret);
		return;
	}

	if (input_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
		LOGE("Bitmap format is not RGB_888 !");
		return;
	}

	if ((ret = AndroidBitmap_lockPixels(env, input_bitmap, &input_pixels)) < 0) {
		LOGE("input_pixels AndroidBitmap_lockPixels() failed ! error=%d", ret);
	}

	/*if ((ret = AndroidBitmap_getInfo(env, output_bitmap, &output_info)) < 0) {
		LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
		return;
	}

	if (output_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
		LOGE("Bitmap format is not RGB_888 !");
		return;
	}

	if ((ret = AndroidBitmap_lockPixels(env, output_bitmap, &output_pixels)) < 0) {
		LOGE("output_pixels AndroidBitmap_lockPixels() failed ! error=%d", ret);
	}*/
	freeVertexList();
	mosaic(input_pixels, 0, bufferWidth, bufferHeight, 0 );
	AndroidBitmap_unlockPixels(env, input_bitmap);
	//AndroidBitmap_unlockPixels(env, output_bitmap);
}
JNIEXPORT void JNICALL Java_com_android_filter_jni_EffectJNI_nativeMakeGradient(JNIEnv * env, jobject  obj, jobject input_bitmap)
{
	AndroidBitmapInfo  input_info;
	void              *input_pixels;
	int                ret;

	if ((ret = AndroidBitmap_getInfo(env, input_bitmap, &input_info)) < 0) {
		LOGE("input_bitmap AndroidBitmap_getInfo() failed ! error=%d", ret);
		return;
	}

	if (input_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
		LOGE("Bitmap format is not RGB_888 !");
		return;
	}

	if ((ret = AndroidBitmap_lockPixels(env, input_bitmap, &input_pixels)) < 0) {
		LOGE("input_pixels AndroidBitmap_lockPixels() failed ! error=%d", ret);
	}
	bufferWidth = input_info.width;
	bufferHeight = input_info.height;
	makeGradient((unsigned char*)input_pixels, input_info.width, input_info.height, 0);
	//mosaic((unsigned char*)input_pixels, (unsigned char*)output_pixels, input_info.width, input_info.height, 0 );

	AndroidBitmap_unlockPixels(env, input_bitmap);
}
JNIEXPORT void JNICALL Java_com_android_filter_jni_EffectJNI_nativeMakeGrid()
{
	makeGrid(bufferWidth, bufferHeight);
}
JNIEXPORT void JNICALL Java_com_android_filter_jni_EffectJNI_nativeFreeMemory()
{
	freeBuffers();
}
JNIEXPORT void JNICALL Java_com_android_filter_jni_EffectJNI_nativeMosaicStyle(JNIEnv *env, jclass cls, int style)
{
	if(style == SQUARES ||
			style == HEXAGONS ||
			style == OCTAGONS ||
			style == TRIANGLES )
	{
		setTileType(style);
	}
}
JNIEXPORT void JNICALL Java_com_android_filter_jni_EffectJNI_nativeTileSize(JNIEnv *env, jclass cls, float tile_size)
{
	if(tile_size >= 15.f && tile_size <= 40.f)
		setTileSize(tile_size);
}
JNIEXPORT void JNICALL Java_com_android_filter_jni_EffectJNI_nativeTileHeight(JNIEnv *env, jclass cls, float  tile_height)
{
	if(tile_height >= 4.f && tile_height < 12.f)
		mvals.tile_height = tile_height;
}
JNIEXPORT void JNICALL Java_com_android_filter_jni_EffectJNI_nativeTileSpacing(JNIEnv *env, jclass cls, float  tile_spacing)
{
	if(tile_spacing >= 1.f && tile_spacing < 3.f)
		mvals.tile_spacing = tile_spacing;
}
JNIEXPORT void JNICALL Java_com_android_filter_jni_EffectJNI_nativeTileNeatness(JNIEnv *env, jclass cls, float  tile_neatness)
{
	if(tile_neatness >= .65f && tile_neatness < 1.95f)
		mvals.tile_neatness = tile_neatness;
}
JNIEXPORT void JNICALL Java_com_android_filter_jni_EffectJNI_nativeLightDirection(JNIEnv *env, jclass cls, float  light_dir)
{
	if(light_dir >= 0.f && light_dir <= 360.f)
		mvals.light_dir = light_dir;
}
JNIEXPORT jbyteArray JNICALL Java_com_android_filter_jni_EffectJNI_nativeGetEdgeBuffer(JNIEnv *env, jclass cls)
{
	jintArray result;
	result = (*env)->NewByteArray(env, bufferWidth * bufferHeight);
	if (result == NULL) {
		return NULL; /* out of memory error thrown */
	}
	int i;
	// fill a temp structure to use to populate the java int array
	unsigned char* edge = getEdgeBuffer();
	//	for (i = 0; i < size; i++) {
	//		fill[i] = edge[i]; // put whatever logic you want to populate the values here.
	//	}
	// move from the temp structure to the java structure
	(*env)->SetByteArrayRegion(env, result, 0, bufferWidth * bufferHeight, edge);
	return result;
}
JNIEXPORT jobject JNICALL Java_com_android_filter_jni_EffectJNI_nativeGetGeometricPointList(JNIEnv *env, jclass cls)
{
	int i, j;
	int num_geo, total_size;
	jboolean b;
	jclass vertexInfo_class = (*env)->FindClass(env, "com/android/filter/jni/VertexInfo");
	if(vertexInfo_class == NULL)
		LOGE("error");
	jmethodID mVertexInfoCtor = (*env)->GetMethodID(env, vertexInfo_class, "<init>", "()V");
	jobject vertexInfoObject = (*env)->NewObject(env, vertexInfo_class, mVertexInfoCtor, "");

	jfieldID num_geometric_field_id = (*env)->GetFieldID(env, vertexInfo_class, "num_geometric", "I");
	jfieldID stride_field_id = (*env)->GetFieldID(env, vertexInfo_class, "stride", "[I");
	jfieldID colors_field_id = (*env)->GetFieldID(env, vertexInfo_class, "geometric_colors", "[I");
	jfieldID vertices_field_id = (*env)->GetFieldID(env, vertexInfo_class, "vertices", "[I");

	GeometricList* gList = getGeometricList();
	Geometric* temp = gList->head;

	num_geo = gList->num_geometric;
	total_size = gList->total_size;
	(*env)->SetIntField(env, vertexInfoObject, num_geometric_field_id, num_geo);

	jintArray numGeoArray = (*env)->NewIntArray(env, num_geo );
	(*env)->SetObjectField(env, vertexInfoObject, stride_field_id,  numGeoArray);
	jint* numGeoElements = (*env)->GetIntArrayElements(env, numGeoArray, &b);
	jint* tempArr = numGeoElements;

	jintArray colorsArray = (*env)->NewIntArray(env, num_geo /* * 4 */);
	(*env)->SetObjectField(env, vertexInfoObject, colors_field_id,  colorsArray);
	jint* colorsElements = (*env)->GetIntArrayElements(env, colorsArray, &b);
	jint* tempArr2 = colorsElements;

	jintArray vertexArray = (*env)->NewIntArray(env, total_size * 2);
	(*env)->SetObjectField(env, vertexInfoObject, vertices_field_id,  vertexArray);
	jint* vertexElements = (*env)->GetIntArrayElements(env, vertexArray, &b);
	jint* tempArr1 = vertexElements;
	while(1)
	{
		if(temp == 0)
		{
			break;
		}
		int num_pts = temp->num_pts;
		int argb = temp->average_color;
		*tempArr++ = num_pts;
		*tempArr2++ = argb;
		/**tempArr2++ = temp->average_color[0];
		*tempArr2++ = temp->average_color[1];
		*tempArr2++ = temp->average_color[2];
		*tempArr2++ = temp->average_color[3];*/
		Vertex* pts = temp->pts;
		if(pts == 0)
		{
			break;
		}
		for(j = 0; j < num_pts; j++)
		{
			*tempArr1++ = (int)pts[j].x;
			*tempArr1++ = (int)pts[j].y;
		}
		temp = temp->next;
	}
//	if(numGeoArray != 0 && vertexArray != 0 && colorsArray != 0)
//	{
		(*env)->ReleaseIntArrayElements(env, numGeoArray, numGeoElements, 0);
		(*env)->ReleaseIntArrayElements(env, vertexArray, vertexElements, 0);
		(*env)->ReleaseIntArrayElements(env, colorsArray, colorsElements, 0);
//	}
	return vertexInfoObject;
}
JNIEXPORT jint JNICALL Java_com_android_filter_jni_EffectJNI_nativeFreeGridVertexList(JNIEnv *env)
{
	freeVertexList();
}
JNIEXPORT jint JNICALL Java_com_android_filter_jni_EffectJNI_nativeSetEdgeBuffer(JNIEnv *env, jclass cls, jbyteArray mGrad, jbyteArray vGrad, jbyteArray hGrad, int width, int height)
{
	jbyte *mg, *hg, *vg;
	jint i = 0;
	int size = 0;
	// get a pointer to the array
	mg = (*env)->GetByteArrayElements(env, mGrad, NULL);

	// do some exception checking
	if (mg == NULL) {
		return -1; /* exception occurred */
	}
	hg = (*env)->GetByteArrayElements(env, hGrad, NULL);

	if (hg == NULL) {
		return -1;
	}
	vg = (*env)->GetByteArrayElements(env, vGrad, NULL);

	if (vg == NULL) {
		return -1;
	}
	bufferWidth = width;
	bufferHeight = height;
	size = width*height;
	unsigned char* mGradBuff = (unsigned char*)malloc(sizeof(unsigned char) * size);
	unsigned char* vGradBuff = (unsigned char*)malloc(sizeof(unsigned char) * size);
	unsigned char* hGradBuff = (unsigned char*)malloc(sizeof(unsigned char) * size);
	memcpy(mGradBuff, mg, size);
	memcpy(vGradBuff, vg, size);
	memcpy(hGradBuff, hg, size);
	setEdgeBuffer(mGradBuff, vGradBuff, hGradBuff);
	// release the memory so java can have it again
	(*env)->ReleaseByteArrayElements(env, vGrad, vg, 0);
	(*env)->ReleaseByteArrayElements(env, hGrad, hg, 0);
	(*env)->ReleaseByteArrayElements(env, mGrad, mg, 0);
	return 0;
}
